#pragma once

#include "mysql.h"
#include "path.h"

class Task
{
protected:
    int clifd_;
    int uid_;
    int code_;
    string path_;

public:
    Task(int clifd, int uid, int code, string path)
        : clifd_(clifd), uid_(uid), code_(code), path_(path)
    {
    }
    virtual void work() = 0;
    virtual ~Task() {};

    IsClose isClose()
    {
        char buf[4];
        int ret = ::recv(clifd_, buf, sizeof(buf), MSG_PEEK);
        if (ret == 0)
        {
            return IsClose::TRUE;
        }
        return IsClose::FALSE;
    }

    IsClose sendERR(const string &msg)
    {
        struct Protocol proto{};
        proto.behavior_ = Behavior::ERR;
        proto.len_ = msg.size();
        strncpy(proto.buf_, msg.c_str(), msg.size());

        int ret = ::send(clifd_, &proto, 4 + 4 + proto.len_, 0);
        if(ret < 0){
#ifdef DEBUG
    cout << "Task sendERR: client is disconnect!" << endl;
#endif
            return IsClose::TRUE;
        }
        return IsClose::FALSE;
    }

    IsClose sendOK(const string &msg)
    {
        struct Protocol proto{};
        proto.behavior_ = Behavior::OK;
        proto.len_ = msg.size();
        strncpy(proto.buf_, msg.c_str(), msg.size());

        int ret = ::send(clifd_, &proto, 4 + 4 + proto.len_, 0);
        if(ret < 0){

#ifdef DEBUG
    cout << "Task sendERR: client is disconnect!" << endl;
#endif
            return IsClose::TRUE;
        }
        return IsClose::FALSE;
    }

    IsClose read_N(char *buf, size_t size)
    {
        size_t total{}, ret{};
        while (total < size)
        {
            ret = ::recv(clifd_, buf + total, size - total, 0);

            if(size != 0 && ret == 0){
                return IsClose::TRUE;
            }
            total += ret;
        }

        return IsClose::FALSE;
    }
};

class PutTask : public Task
{
private:
    string md5_;

public:
    PutTask(int fd, int uid, int code, string path, string md5)
        : Task(fd, uid, code, path), md5_(md5)
    {
#ifdef DEBUG
        cout << "PutTask: " << code << endl;
#endif
    }

    IsClose recvProto(struct Protocol &proto)
    {
        IsClose closed = IsClose::FALSE;
        closed = read_N((char *)&proto.behavior_, sizeof(proto.behavior_));
        closed = read_N((char *)&proto.len_, sizeof(proto.len_));
        closed = read_N(proto.buf_, proto.len_);

        if(proto.len_ < 4095){
#ifdef DEBUG
    cout << "PutTask recvProto proto.len_: is done!" << endl;
#endif
            closed = IsClose::TRUE;
        }

        return closed;
    }

    // TODO: 完善删除连接文件不会删除真实文件
    void work()
    {
        // TODO: 实现妙传
        shared_ptr<File> file = Mysql::queryFile(md5_);

        shared_ptr<VirtualDirec> dir = Mysql::queryFile(uid_, code_, Path::getFile(path_));

#ifdef DEBUG
    cout << "work code: " << code_ << endl;
        cout << "work Path::getFile(): " << Path::getFile(path_) << endl;
#endif

        if (dir && dir->fname == Path::getFile(path_))
        {
            sendERR("file is exists!");
            ::close(clifd_);
            return;
        }

        if (!file)
        {
#ifdef DEBUG
            cout << "work: " << path_.c_str() << endl;
#endif

            int ffd = open(path_.c_str(), O_RDWR | O_CREAT, 0666);
            ERR_CHECK(ffd, -1, "open")

            struct Protocol proto;
            bool running = true;
            IsClose closed = IsClose::FALSE;
            while (closed == IsClose::FALSE)
            {
                bzero(&proto, sizeof(proto));
                closed = recvProto(proto);

                ::write(ffd, proto.buf_, proto.len_);
            }

            ::close(ffd);

            string md5;
            Path::getMd5Sum(path_, md5, Path::getFileSize(path_));

#ifdef DEBUG
            cout << "PutTask work: " << md5 << endl;
#endif

            if (md5_ != md5)
            {
                int ret = unlink(path_.c_str());
                ERR_CHECK(ret, -1, "work unlink")

                // TODO: 完成提醒客户端
                ::close(clifd_);
                return;
            }

            Mysql::insertFile(path_, md5);
        }
        else
        {

#ifdef DEBUG
    cout << "work else file->fname: " << file->fname << endl;
#endif

            Mysql::updateFile(file->fid, file->linked + 1);
            int ret = link(file->fname.c_str(), path_.c_str());
            ERR_CHECK(ret, -1, "link")
        }
        Mysql::insertFile(uid_, code_, Path::getFile(path_), md5_);

        ::close(clifd_);

#ifdef DEBUG
        cout << "Put " << path_ << " is done!" << endl;
#endif
    }
};



class GetTask: public Task
{
private:
    size_t size_; 
    string md5sum_;
public:
    GetTask(int fd, int uid, string fpath, int code, size_t size, string md5sum)
    : Task(fd, uid, code, fpath), size_(size), md5sum_(md5sum)
    {}

    void work()
    {
        IsClose closed = IsClose::FALSE;
        // 从数据库中爬取到当前文件的位置
        shared_ptr<VirtualDirec> dir = Mysql::queryFile(uid_, code_, Path::getFile(path_));
        // 判断之前的文件是否合法，并向客户端发送
        if(!dir || dir->md5sum.empty()){
            sendERR("file is not exists!");
            ::close(clifd_);
            return;
        }

        shared_ptr<File> file = Mysql::queryFile(dir->md5sum);
        string halfMd5sum;
        if(size_ != 0){
            Path::getMd5Sum(path_, halfMd5sum, size_);
            if(halfMd5sum != md5sum_){
                sendERR("file error, please delete local file, try again!");
                ::close(clifd_);
                return;
            }
        }

        closed = sendOK(dir->md5sum);
        if(closed == IsClose::TRUE){
            ::close(clifd_);
            return;
        }

        // 向客户端传输数据
        int fd = open(file->fname.c_str(), O_RDONLY);
        ERR_CHECK(fd, -1, "GetTask work open")

        off_t offset = lseek(fd, size_, SEEK_SET);
        ERR_CHECK(offset, -1, "GetTask work open")

        struct Protocol proto;
        int ret {};
        while(IsClose::FALSE == closed){
            bzero(&proto, sizeof(proto));
            ret = ::read(fd, proto.buf_, sizeof(proto.buf_) - 1);

            closed = sendProto(proto, ret);
            if(ret < sizeof(proto.buf_) - 1){
                closed = IsClose::TRUE;
            }
        }
        // 传输完毕直接断开连接
        close(fd);
        close(clifd_);

#ifdef DEBUG
    cout << "GetTask work " << file->fname << " is done!" << endl;
#endif    

    }

    IsClose sendProto(struct Protocol &proto, int len)
    {
        proto.behavior_ = Behavior::TRANSFER;
        proto.len_ = len; 
        int ret = ::send(clifd_, &proto, 4 + 4 + proto.len_, 0);
        if(ret < 0){
            return IsClose::TRUE;
        }
        return IsClose::FALSE;
    }
};